/*****************************************************************************
 *
 * mtdev - Multitouch Protocol Translation Library (MIT license)
 *
 * Copyright (C) 2010 Henrik Rydberg <rydberg@euromail.se>
 * Copyright (C) 2010 Canonical Ltd.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 ****************************************************************************/

/*
Usage :
$ vi /etc/rc/local
# Add
/root/mtg/gestures/mtg > /var/log/mtg.log 2>&1 &
# Before exit 0
*/

#include <mtdev.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <syslog.h>
#include <fcntl.h>
#include <time.h>

#define MTG_DEV_NAME		"\"Focaltech Systems FT5926 MultiTouch\""
#define MTG_DEV_EVENT		"/dev/input/event"

#define MTG_XINPUT_CMD		"DISPLAY=:0 xinput "
#define MTG_XDOTOOL_CMD		"DISPLAY=:0 xdotool "
#define MTG_XHOST_CMD		"sudo -u test DISPLAY=:0 xhost +"

#define MTG_INF(format, ...)	\
{ \
	fprintf(stdout, "%-15s %-3d : " format, __func__, __LINE__, ##__VA_ARGS__); \
	syslog(LOG_INFO, "%-15s %-3d : " format, __func__, __LINE__, ##__VA_ARGS__); \
}


#define MTG_OUT(format, ...)	\
{ \
	fprintf(stdout, format, ##__VA_ARGS__); \
	syslog(LOG_INFO, format, ##__VA_ARGS__); \
}


/* year-proof millisecond event time */
typedef __u64 mtg_ms_t;

static const char * const mtg_event_type_str[] = 
{ 
	[EV_SYN]		= "syn", 
	[EV_KEY]		= "key", 
	[EV_REL]		= "rel", 
	[EV_ABS]		= "abs", 
	[EV_MSC]		= "msc", 
	[EV_SW]			= "sw", 
	[EV_LED]		= "led", 
	[EV_SND]		= "snd", 
	[EV_REP]		= "rep", 
	[EV_FF]			= "ff", 
	[EV_PWR]		= "pwr", 
	[EV_FF_STATUS]	= "ff_status", 
	[EV_MAX]		= "max", 
	[EV_CNT]		= "cnt", 
};

static const char * const mtg_event_code_str[] = 
{
	[REL_X]					= "x", 
	[REL_Y]					= "y", 
	
	[BTN_TOUCH]				= "touch", 
	
	[ABS_MT_SLOT]			= "mt_slot", 
	
	[ABS_MT_TOUCH_MAJOR]	= "mt_major", 
	[ABS_MT_TOUCH_MINOR]	= "mt_minor", 
	
	[ABS_MT_WIDTH_MAJOR]	= "mt_wmajor", 
	[ABS_MT_WIDTH_MINOR]	= "mt_wminor", 
	
	[ABS_MT_ORIENTATION]	= "mt_orientation", 
	
	[ABS_MT_POSITION_X]		= "mt_x", 
	[ABS_MT_POSITION_Y]		= "mt_y", 
	
	[ABS_MT_TOOL_TYPE]		= "mt_tool_type", 
	
	[ABS_MT_BLOB_ID]		= "mt_blob_id", 
	
	[ABS_MT_TRACKING_ID]	= "mt_tracking_id", 
	
	[ABS_MT_PRESSURE]		= "mt_pressure", 
	
	[ABS_MT_TOOL_X]			= "mt_tool_x", 
	[ABS_MT_TOOL_Y]			= "mt_tool_y", 
};


int mtg_popen(char* out_buf, const char* format, ...)
{
	char cmd[1024] = "\0";
	char buf[8192] = "\0";
	
	va_list args;
	va_start(args, format);
	vsprintf(cmd, format, args);
	va_end(args);
	
	FILE* fp = popen(cmd, "r");
	if(fp == NULL)
	{
		MTG_INF("popen command \"%s\" failed, errno=%d.\n", cmd, errno);
		return -1;
	}
	
	int ret = fread(buf, sizeof(buf), 1, fp);
	if(ferror(fp) != 0)
	{
		MTG_INF("fread command \"%s\" popen ouput failed, errno=%d.\n", cmd, errno);
		pclose(fp);
		return -1;
	}
	
	if(out_buf == (void*)-1)
	{
	}
	else if(out_buf != NULL)
	{
		strcpy(out_buf, buf);
	}
	else
	{
		MTG_OUT("cmd : %s\n", cmd);
		MTG_OUT("out : %s\n", buf);
	}
	
	pclose(fp);
	return 0;
}


int mtg_dev_is_enable(const char* dev_name)
{
	char out_buf[16];
	
	if(mtg_popen(out_buf, MTG_XINPUT_CMD "list-props %s | grep \"Device Enabled\" | awk '{print $4}'", dev_name) != 0)
	{
		MTG_INF("get device %s enable status failed.\n", dev_name);
		return -1;
	}
	
	return out_buf[0] == '1' ? 1 : 0;
}


#if 1
int mtg_dev_enable(const char* dev_name)
{
	mtg_popen((void*)-1, MTG_XINPUT_CMD "enable %s", dev_name);
	
	if(mtg_dev_is_enable(dev_name) == 1)
	{
		MTG_INF("enable %s ok.\n", dev_name);
		return 0;
	}
	
	MTG_INF("enable %s failed.\n", dev_name);
	return -1;
}


int mtg_dev_disable(const char* dev_name)
{
	mtg_popen((void*)-1, MTG_XINPUT_CMD "disable %s", dev_name);
	
	if(mtg_dev_is_enable(dev_name) == 0)
	{
		MTG_INF("disable %s ok.\n", dev_name);
		return 0;
	}
	
	MTG_INF("disable %s failed.\n", dev_name);
	return -1;
}
#else
int mtg_dev_enable(const char* dev_name) { return 0; } 
int mtg_dev_disable(const char* dev_name) { return 0; } 
#endif


int mtg_dev_get(const char* dev_name, char* dev_path)
{
	if(mtg_popen(dev_path, MTG_XINPUT_CMD "list-props %s | grep \"Device Node\" | awk -F\\\" '{print $2}'", dev_name) != 0)
	{
		MTG_INF("get %s event path failed.\n", dev_name);
		return -1;
	}
	
	dev_path[strlen(dev_path) - 1] = '\0';
	MTG_OUT("device %s path is \"%s\".\n", dev_name, dev_path);
	return 0;
}


/*static int use_event(const struct input_event *ev)
{
#if 0
	return ev->type == EV_ABS && mtdev_is_absmt(ev->code);
#else
	return 1;
#endif
}*/


#define MTG_PROPS_CHECK(dev, name)	if(mtdev_has_mt_event(dev, name)) MTG_OUT("   %s\n", #name)

static void mtg_props_show(const struct mtdev *dev)
{
	MTG_OUT("supported mt events:\n");
	
	MTG_PROPS_CHECK(dev, ABS_MT_SLOT);
	MTG_PROPS_CHECK(dev, ABS_MT_TOUCH_MAJOR);
	MTG_PROPS_CHECK(dev, ABS_MT_TOUCH_MINOR);
	MTG_PROPS_CHECK(dev, ABS_MT_WIDTH_MAJOR);
	MTG_PROPS_CHECK(dev, ABS_MT_WIDTH_MINOR);
	MTG_PROPS_CHECK(dev, ABS_MT_ORIENTATION);
	MTG_PROPS_CHECK(dev, ABS_MT_POSITION_X);
	MTG_PROPS_CHECK(dev, ABS_MT_POSITION_Y);
	MTG_PROPS_CHECK(dev, ABS_MT_TOOL_TYPE);
	MTG_PROPS_CHECK(dev, ABS_MT_BLOB_ID);
	MTG_PROPS_CHECK(dev, ABS_MT_TRACKING_ID);
	MTG_PROPS_CHECK(dev, ABS_MT_PRESSURE);
	MTG_PROPS_CHECK(dev, ABS_MT_DISTANCE);
}


typedef struct mtg_event
{
	mtg_ms_t ms;
	
	int slot;
	int id;
	int key;
	
	int x;
	int y;
	
	int mt_x;
	int mt_y;
	
	int tool_x;
	int tool_y;
	
} mtg_event_t;


int mtg_trans(const struct input_event const *ev, mtg_event_t* const te)
{
	if(ev->type == EV_ABS)
	{
		switch(ev->code)
		{
			case ABS_MT_SLOT		: te->slot		= ev->value; break;
			case ABS_MT_TRACKING_ID	: te->id		= ev->value; break;
			
			case REL_X				: te->x			= ev->value; break;
			case REL_Y				: te->y			= ev->value; break;
			
			case ABS_MT_POSITION_X	: te->mt_x		= ev->value; break;
			case ABS_MT_POSITION_Y	: te->mt_y		= ev->value; break;
			
			case ABS_MT_TOOL_X		: te->tool_x	= ev->value; break;
			case ABS_MT_TOOL_Y		: te->tool_y	= ev->value; break;
			
			default : 
			{
				if(mtg_event_code_str[ev->code] != 0)
				{
					MTG_OUT("abs code %15s \n", mtg_event_code_str[ev->code]);
				}
				else
				{
					MTG_OUT("abs code %04x \n", ev->code);
				}
				break;
			}
		}
		
		return 0;
	}
	
	if(ev->type == EV_KEY)
	{
		switch(ev->code)
		{
			case BTN_TOUCH			: te->key		= ev->value; break;
			
			default : 
			{
				if(mtg_event_code_str[ev->code] != 0)
				{
					MTG_OUT("key code %15s \n", mtg_event_code_str[ev->code]);
				}
				else
				{
					MTG_OUT("key code %04x \n", ev->code);
				}
				break;
			}
		}
		
		return 0;
	}
	
	if(ev->type != EV_SYN)
	{
		MTG_OUT("unsupport type %04x, ", ev->type);
		return 0;
	}
	
	if((te->slot > 1) || (((te->x == -1) || (te->y == -1) || (te->mt_x == -1) || 
			(te->mt_y == -1) || (te->tool_x == -1) || (te->tool_y == -1)) && (te->key != 0)))
	{
		return -1;
	}
	
	te->ms	= ev->time.tv_usec / 1000 + ev->time.tv_sec * 1000;
	return 1;
}


static mtg_event_t mtg_pool[256];
static int mtg_index	= 0;
static int mtg_keep		= 1;
static int mtg_touch	= 0;

#define MTG_KEEP_OFF	30

int mtg_proc(const mtg_event_t* const te)
{
	if((mtg_index == 0) && (te->key != 1))
	{
		return 0;
	}
	
	if(te->key == 1)
	{
		MTG_OUT("\n");
		mtg_index	= 0;
		mtg_keep	= 1;
		mtg_touch	= 0;
	}
	
	MTG_OUT("%-3d, %012llu, slot %2d, id %5d, key %5d, [%5d, %5d] [%5d, %5d] [%5d, %5d]\n", mtg_index, te->ms, 
			te->slot, te->id, te->key, te->x, te->y, te->mt_x, te->mt_y, te->tool_x, te->tool_y);
	
	memcpy(&mtg_pool[mtg_index++], te, sizeof(*te));
	
	if((te->x != -1) && (te->y != -1) && (mtg_keep != 0))
	{
		const int x_off = te->x - mtg_pool[0].x;
		const int y_off = te->y - mtg_pool[0].y;
		
		if((x_off > MTG_KEEP_OFF) || (x_off < -MTG_KEEP_OFF) || (y_off > MTG_KEEP_OFF) || (y_off < -MTG_KEEP_OFF))
		{
			mtg_keep = 0;
		}
	}
	
	if(te->key == -1)
	{
		if(mtg_keep == 0)
		{
			if(mtg_touch == 0)
			{
				mtg_popen(NULL, MTG_XDOTOOL_CMD "mousemove %d %d mousedown 1", mtg_pool[0].x, mtg_pool[0].y);
				mtg_touch = 1;
			}
			
			mtg_popen(NULL, MTG_XDOTOOL_CMD "mousemove %d %d", te->x, te->y);
		}
		
		return 0;
	}
	
	if(te->key == 0)
	{
		MTG_OUT("\n");
		
		if(mtg_keep != 0)
		{
			const mtg_ms_t press_ms = te->ms - mtg_pool[0].ms;
			
			if(press_ms > 300)
			{
				MTG_OUT("long press %lldms.\n", press_ms);
				mtg_popen(NULL, MTG_XDOTOOL_CMD "mousemove %d %d click 3", mtg_pool[0].x, mtg_pool[0].y);
			}
			else if(press_ms > 15)
			{
				MTG_OUT("short press %lldms.\n", press_ms);
				mtg_popen(NULL, MTG_XDOTOOL_CMD "mousemove %d %d click 1", mtg_pool[0].x, mtg_pool[0].y);
			}
			else
			{
				MTG_OUT("skip press %lldms.\n", press_ms);
			}
		}
		else
		{
			if(mtg_touch == 1)
			{
				mtg_popen(NULL, MTG_XDOTOOL_CMD "mousemove %d %d mouseup 1", 
						mtg_pool[mtg_index - 2].x, mtg_pool[mtg_index - 2].y);
				mtg_touch = 0;
			}
			
		}
		
		mtg_index	= 0;
		mtg_keep	= 1;
		mtg_touch	= 0;
	}
	
	return 0;
}


static int mtg_is_break = 0;

static void mtg_signal(int signum)
{
	mtg_is_break = 1;
}


#define MTG_IDLE_MS		100
#define MTG_SKIP_MS		500

#define MTG_WM_BIN		"gnome-session-binary"

int main(int argc, char *argv[])
{
	time_t tm;
	
	if(signal(SIGINT, mtg_signal) == SIG_ERR)
	{
		MTG_INF("set signal failed.\n");
		return -1;
	}
	
	while(mtg_is_break == 0)
	{
		char buf[1024];
		if(mtg_popen(buf, "ps -aux | grep %s | wc -l", MTG_WM_BIN) == 0)
		{
			if(atoi(buf) >= 2)
			{
				MTG_OUT("process \"%s\" number is %d.\n", MTG_WM_BIN, atoi(buf) - 1);
				break;
			}
		}
		
		MTG_OUT("wait for \"%s\" process, %s", MTG_WM_BIN, (tm=time(NULL), asctime(localtime(&tm))));
		sleep(2);
	}
	MTG_OUT("process \"%s\" has been started, %s", MTG_WM_BIN, (tm=time(NULL), asctime(localtime(&tm))));
	
	char dev_path[256];
	if(argc >= 2)
	{
//		MTG_INF("Usage: mtdev <device>\n");
		strcpy(dev_path, argv[1]);
	}
	else
	{
		while(mtg_is_break == 0)
		{
			mtg_popen(NULL, MTG_XHOST_CMD);
			
			if(mtg_dev_get(MTG_DEV_NAME, dev_path) != 0)
			{
				MTG_INF("device %s path get failed.\n", MTG_DEV_NAME);
			}
			else if(strstr(dev_path, MTG_DEV_EVENT) != NULL)
			{
				MTG_OUT("device %s path \"%s\" get ok, %s", MTG_DEV_NAME, dev_path, 
						(tm=time(NULL), asctime(localtime(&tm))));
				break;
			}
			
			MTG_OUT("wait for device \"%s\", %s", MTG_DEV_NAME, (tm=time(NULL), asctime(localtime(&tm))));
			sleep(2);
		}
	}
	
	if(mtg_dev_disable(MTG_DEV_NAME) != 0)
	{
		MTG_INF("disable %s failed.\n", MTG_DEV_NAME);
		return -1;
	}
	
	int fd = open(dev_path, O_RDONLY | O_NONBLOCK);
	if(fd < 0)
	{
		MTG_INF("could not open device.\n");
		mtg_dev_enable(MTG_DEV_NAME);
		return -1;
	}
	MTG_OUT("open %s ok.\n", dev_path);
	
	if(ioctl(fd, EVIOCGRAB, 1))
	{
		MTG_INF("could not grab the device.\n");
		close(fd);
		mtg_dev_enable(MTG_DEV_NAME);
		return -1;
	}
	MTG_OUT("grab %s ok.\n", dev_path);
	
	memset(mtg_pool, 255, sizeof(mtg_pool));
	
	struct mtdev dev;
	int ret = mtdev_open(&dev, fd);
	if(ret)
	{
		MTG_INF("could not open device: %d\n", ret);
		ioctl(fd, EVIOCGRAB, 0);
		close(fd);
		mtg_dev_enable(MTG_DEV_NAME);
		return -1;
	}
	MTG_OUT("open %s mtdev ok.\n", dev_path);
	
	mtg_props_show(&dev);
	
	mtg_event_t te, bak;
	memset(&te, 255, sizeof(te));
	memset(&bak, 255, sizeof(bak));
	
	while(mtg_is_break == 0)
	{
		if(mtdev_idle(&dev, fd, MTG_IDLE_MS) != 0)
		{
			if(bak.key == 0)
			{
				mtg_proc(&bak);
				memset(&bak, 255, sizeof(bak));
			}
			continue;
		}
		
		struct input_event ev;
		while(mtdev_get(&dev, fd, &ev, 1) > 0)
		{
			int ret = mtg_trans(&ev, &te);
			
			if(ret == 0)
			{
				continue;
			}
			
			if(ret == 1)
			{
				if(te.key == 1)
				{
					if(bak.key == 0)
					{
						const int x_off = (te.x - bak.x) >> 2;
						const int y_off = (te.y - bak.y) >> 2;
						
						if(((x_off > -MTG_KEEP_OFF) && (x_off < MTG_KEEP_OFF) && 
							(y_off > -MTG_KEEP_OFF) && (y_off < MTG_KEEP_OFF)) || 
							((te.ms - bak.ms) > MTG_SKIP_MS))
						{
							mtg_proc(&bak);
							memset(&bak, 255, sizeof(bak));
							
							mtg_proc(&te);
						}
					}
					else
					{
						mtg_proc(&te);
					}
				}
				else if(te.key == 0)
				{
					memcpy(&bak, &te, sizeof(te));
				}
				else
				{
					mtg_proc(&te);
				}
			}
			
			memset(&te, 255, sizeof(te));
		}
	}
	
	mtdev_close(&dev);
	ioctl(fd, EVIOCGRAB, 0);
	close(fd);
	mtg_dev_enable(MTG_DEV_NAME);
	
	MTG_OUT("\nmtg exit.\n");
	return 0;
}


